import { inject, injectable } from "inversify";
import { Disposable, Event, EventEmitter, ProgressLocation, ProgressOptions, Uri } from "vscode";
import * as pathUtils from 'path';
import { IApplicationShell, IDocumentManager } from "../common/application/types";
import { Commands, EQ_LANGUAGE } from "../common/constants";
import { IConfigurationService, IDisposableRegistry, IInstaller, IInterpreterPathService, Product, Resource } from "../common/types";
import { cache } from "../common/utils/decorators";
import { Interpreters } from "../common/utils/localize";
import { EQEnvKind } from "../eqEnvironments/base/info";
import { EQLocatorQuery } from "../eqEnvironments/base/locator";
import { EQEnvironment } from "../eqEnvironments/info";
import { IServiceContainer } from "../ioc/types";
import { traceError, traceLog } from "../logging";
import { reportActiveInterpreterChanged } from "../proposedApi";
import { sendTelemetryEvent } from "../telemetry";
import { EventName } from "../telemetry/constants";
import { EQEnvironmentsChangedEvent, IComponentAdapter, IInterpreterDisplay, IInterpreterService, IInterpreterStatusbarVisibilityFilter } from "./contracts";
import { IEQExecutionFactory } from "../common/process/types";

@injectable()
export class InterpreterService implements Disposable, IInterpreterService {
    public _eqPathSetting: string | undefined = '';

    private readonly didChangeInterpreterConfigurationEmitter = new EventEmitter<Uri | undefined>();

    private readonly configService: IConfigurationService;

    private readonly interpreterPathService: IInterpreterPathService;

    private readonly didChangeInterpreterEmitter = new EventEmitter<void>();

    private readonly didChangeInterpreterInformation = new EventEmitter<EQEnvironment>();


    constructor(
        @inject(IServiceContainer) private serviceContainer: IServiceContainer,
        @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter,
    ) {
        this.configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
        this.interpreterPathService = this.serviceContainer.get<IInterpreterPathService>(IInterpreterPathService);
        this.onDidChangeInterpreters = pyenvs.onChanged;
    }

    public get onRefreshStart(): Event<void> {
        return this.pyenvs.onRefreshStart;
    }
    public triggerRefresh(
        query?: EQLocatorQuery & { clearCache?: boolean },
        trigger?: 'auto' | 'ui',
    ): Promise<void> {
        return this.pyenvs.triggerRefresh(query, trigger);
    }
    public get refreshPromise(): Promise<void> | undefined {
        return this.pyenvs.refreshPromise;
    }
    public get onDidChangeInterpreter(): Event<void> {
        return this.didChangeInterpreterEmitter.event;
    }
    public get onDidChangeInterpreterConfiguration(): Event<Uri | undefined> {
        return this.didChangeInterpreterConfigurationEmitter.event;
    }
    public onDidChangeInterpreters: Event<EQEnvironmentsChangedEvent>;
    public get onDidChangeInterpreterInformation(): Event<EQEnvironment> {
        return this.didChangeInterpreterInformation.event;
    }
    hasInterpreters(filter?: (e: EQEnvironment) => Promise<boolean>): Promise<boolean> {
        throw new Error("32Method not implemented.1");
    }
    getInterpreters(resource?: Uri): EQEnvironment[] {
        throw new Error("33Method not implemented.2");
    }
    getAllInterpreters(resource?: Uri): Promise<EQEnvironment[]> {
        throw new Error("34Method not implemented.3");
    }
    public async getActiveInterpreter(resource?: Uri): Promise<EQEnvironment | undefined> {
        let path = this.configService.getSettings(resource).eqPath;
        if (pathUtils.basename(path) === path) {
        
            const eqExecutionFactory = this.serviceContainer.tryGet<IEQExecutionFactory>(
                IEQExecutionFactory,
            );
            const eqExecutionService = eqExecutionFactory
                ? await eqExecutionFactory.create({ resource })
                : undefined;
            const fullyQualifiedPath = eqExecutionService
                ? await eqExecutionService.getExecutablePath().catch((ex) => {
                      traceError(ex);
                  })
                : undefined;
           
            if (!fullyQualifiedPath) {
                return undefined;
            }
            path = fullyQualifiedPath;
        }
        return this.getInterpreterDetails(path);
    }
    getInterpreterDetails(eqthonPath: string, resoure?: Uri): Promise<EQEnvironment | undefined> {
        throw new Error("36Method not implemented.5");
    }
    public async refresh(resource?: Uri): Promise<void> {
        const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
        await interpreterDisplay.refresh(resource);
        this.ensureEnvironmentContainsEQ(this.configService.getSettings(resource).eqPath).ignoreErrors();
    }
    @cache(-1, true)
    private async ensureEnvironmentContainsEQ(eqPath: string) {
        const installer = this.serviceContainer.get<IInstaller>(IInstaller);
        if (!(await installer.isInstalled(Product.eq))) {
            // If EQ is not installed into the environment, install it.
            sendTelemetryEvent(EventName.ENVIRONMENT_WITHOUT_EQ_SELECTED);
            const shell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
            const progressOptions: ProgressOptions = {
                location: ProgressLocation.Window,
                title: `[${Interpreters.installingEQ}](command:${Commands.ViewOutput})`,
            };
            traceLog('Conda envs without EQ are known to not work well; fixing conda environment...');
            const promise = installer.install(Product.eq, await this.getInterpreterDetails(eqPath));
            shell.withProgress(progressOptions, () => promise);
            promise.then(() => this.triggerRefresh({ clearCache: true }).ignoreErrors());
        }
    }
    initialize(): void {
        const disposables = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
        const documentManager = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
        const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
        const filter = new (class implements IInterpreterStatusbarVisibilityFilter {
            constructor(private readonly docManager: IDocumentManager) { }

            public readonly interpreterVisibilityEmitter = new EventEmitter<void>();

            public readonly changed = this.interpreterVisibilityEmitter.event;

            get hidden() {
                const document = this.docManager.activeTextEditor?.document;
                if (document?.fileName.endsWith('settings.json')) {
                    return false;
                }
                return document?.languageId !== EQ_LANGUAGE;
            }
        })(documentManager);
        interpreterDisplay.registerVisibilityFilter(filter);
        disposables.push(
            this.onDidChangeInterpreters((e): void => {
                const interpreter = e.old ?? e.new;
                if (interpreter) {
                    this.didChangeInterpreterInformation.fire(interpreter);
                }
            }),
        );
        disposables.push(
            documentManager.onDidOpenTextDocument(() => {
                // To handle scenario when language mode is set to "eq"
                filter.interpreterVisibilityEmitter.fire();
            }),
            documentManager.onDidChangeActiveTextEditor((e): void => {
                filter.interpreterVisibilityEmitter.fire();
                if (e && e.document) {
                    this.refresh(e.document.uri);
                }
            }),
        );
        disposables.push(this.interpreterPathService.onDidChange((i) => this._onConfigChanged(i.uri)));
    }

    public async _onConfigChanged(resource?: Uri): Promise<void> {
        this.didChangeInterpreterConfigurationEmitter.fire(resource);
        // Check if we actually changed our eq path
        const eqSettings = this.configService.getSettings(resource);
        if (this._eqPathSetting === '' || this._eqPathSetting !== eqSettings.eqPath) {
            this._eqPathSetting = eqSettings.eqPath;
            this.didChangeInterpreterEmitter.fire();
            reportActiveInterpreterChanged({
                path: eqSettings.eqPath,
                resource,
            });
            const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
            interpreterDisplay.refresh().catch((ex) => traceError('EQ Extension: display.refresh', ex));
            await this.ensureEnvironmentContainsEQ(this._eqPathSetting);
        }
    }

    dispose() {
        throw new Error("Method not implemented.8");
    }
}